/**
 * Copyright (c) 2012, David Varbel
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 * 
 * 1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * 2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.epistrata.neutraltheory;

import java.util.Random;

public abstract class Critter {
	
	protected NeutralTheoryModel model = null;
	protected Random random;

	private double[] location = new double[2];
	protected Grid.GridCell currentGridCell = null;
	private int speciesIndex = -1;
	
	protected Critter() {
		location[0] = -1.0d;
		location[1] = -1.0d;
	}


	/**
	 * Set this critter's location to a random point within the spacial mating
	 * distance of the argument.
	 * 
	 * @param aroundLocation
	 */
	protected void randomizeLocation(double[] aroundLocation) {
		assert aroundLocation != null;

		//note, random radius uses sqrt to compensate for grouping at center of circle
		double randomRadius =  Math.sqrt(random.nextDouble()) * model.getDispersalDistance();
		double randomAngle = random.nextDouble() * Math.PI * 2.0d;
		
		double deltaX = Math.cos(randomAngle) * randomRadius; 
		double deltaY = Math.sin(randomAngle) * randomRadius;
		
		double x = aroundLocation[0] + deltaX;
		double y = aroundLocation[1] + deltaY;
		
		//world topography is a taurus
		if (x < 0 || x >= model.getWorldXSize()) {
			x = x < 0 ? x + model.getWorldXSize() : x - model.getWorldXSize();
		}
		if (y < 0 || y >= model.getWorldYSize()) {
			y = y < 0 ? y + model.getWorldYSize() : y - model.getWorldYSize();
		}
		
		setLocation(x, y);
	}
	
	/**
	 * @return the location
	 */
	public double[] getLocation() {
		if (location[0] == -1.0d && location[1] == -1.0d) {
			throw new IllegalStateException("Location has not yet been initialized.");
		}
		return location.clone();
	}

	/**
	 * @param location the location to set
	 */
	public void setLocation(double x, double y) {
		assert location != null;
		assert model != null;
				
		location[0] = x;
		location[1] = y;
		//update location of critter in grid 
		currentGridCell = model.getGrid().updateCritterLocation(this);
	}

	/**
	 * @return the currentGridCell
	 */
	public Grid.GridCell getCurrentGridCell() {
		return currentGridCell;
	}

	public int getSpeciesIndex() {
		return speciesIndex;
	}

	public void setSpeciesIndex(int speciesIndex) {
		this.speciesIndex = speciesIndex;
	}

	public abstract Critter mateWith(Critter mate);	
	
	public abstract Critter mateOpportunistWith(Critter mate, Critter earlyDeathCritter);
	
	public abstract Critter clone();
	
	/**
	 * Check if this critter is in the same species with the other critter by
	 * comparing their genes and determining if the total distance is smaller
	 * than the allowed genetic difference in the configuration.
	 * 
	 * @param other a Critter to compare
	 * @param maxDistance maximum allowed distance
	 * @return true or false
	 */
	public abstract boolean isSameSpecies(Critter other, int maxDistance);
	
	/**
	 * Check if this critter is within the maximum mating distance of other critter
	 * 
	 * @param other the other Critter to compare
	 * @param maxDistance maximum allowed distance between one and two
	 * @return true or false
	 */
	public boolean isWithinMatingDistance(Critter other, double maxDistance) {
		
		double distance = Math.sqrt((this.location[0] - other.location[0]) * (this.location[0] - other.location[0]) +
						  			(this.location[1] - other.location[1]) * (this.location[1] - other.location[1]));
		
		if (distance <= maxDistance) {
			return true;
		}
		else {
			return false;
		}
	}
	
	/**
	 * Return a double[] representing this critter's genome for use in principal component analysis.
	 * Each double must be between 0 and 1, inclusive.
	 * 
	 * @return double array representing critter's genome.
	 */
	public abstract double[] getDoubleGenomeForPca();

}
